package cn.bbstone.pisces2.client.handler;

import cn.bbstone.pisces2.client.base.ClientCache;
import cn.bbstone.pisces2.client.task.impl.FileTask;
import cn.bbstone.pisces2.client.util.MsgUtil;
import cn.bbstone.pisces2.comm.BFileCmd;
import cn.bbstone.pisces2.comm.StatusEnum;
import cn.bbstone.pisces2.comm.cache.ClientFliIndexCache;
import cn.bbstone.pisces2.proto.rsp.RspFile;
import cn.bbstone.pisces2.util.CipherUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class RspFileDataHandler implements ClientHandler {
    private static Logger log = LoggerFactory.getLogger(RspFileDataHandler.class);

    /**
     * fli.idx max fileNo
     */
    private long count = 0;

    @Override
    public void handle(ChannelHandlerContext ctx, RspFile rspFile) {
        long fileNo = rspFile.getFileNo();
        log.debug("+++++ recv a new chunk for fileNo: {}, chunkNo: {}.", fileNo, rspFile.getChunkNo());
        FileTask fileTask = ClientCache.getTask(fileNo);
        if (fileTask == null) {
            log.error("fileTask for fileNo({}) not found. runningTask keys: {}", fileNo, ClientCache.runningTaskInfo());
            throw new RuntimeException("not found FileTask to handler file data.");
        }
        // check recv data chunk whether correct
        if (rspFile.getBodyLen() > 0) { // when bodyLen == 0, means file with empty data
            String bodyChecksum = rspFile.getChecksum();
            String bodyDataChecksum = CipherUtil.md5(rspFile.getBodyData());
//            log.info("[{}]fileNo: {}, chunkNo: {}, bodyChecksum: {}, bodyDataChecksum: {}, bodyData: {}",
//                bodyChecksum.equals(bodyDataChecksum), fileNo, rspFile.getChunkNo(), bodyChecksum, bodyDataChecksum, CipherUtil.hex(rspFile.getBodyData()));
            if (!bodyChecksum.equals(bodyDataChecksum)) {
                log.error("recv file data chunk checksum error.");
                return;
            }
        } else {
            log.warn("recv empty chunk data for fileNo: {}, chunkNo: {}", fileNo, rspFile.getChunkNo());
        }
        // save file data
        StatusEnum status = fileTask.appendFileData(rspFile);
//        StatusEnum status = fileTask.appendFileData(rspFile.getBodyData());
        //
        if (status == StatusEnum.COMPLETED || status == StatusEnum.CONTINUE) {
            log.debug("+++++ file(fileNo: {}, chunkNo: {}, rpath: {}) transfer complete. ", fileNo, rspFile.getChunkNo(), ClientFliIndexCache.getClientIndexNotUpdatePos(fileNo).getRpath());
            // when one chunk recv completed, send FILE_DATA_ACK REQ to server, if server rec this ack, will continue chunk data dilivery
            sendReqFileDataAck(ctx, rspFile.getFileNo());
            log.info("receive total progress: {}/{}, file progress: {}/{}, fileSize: {} B, cost: {} ms. path: {}",
                    rspFile.getFileNo(), ClientCache.getSpCount(), rspFile.getChunkNo(), fileTask.chunks(), fileTask.fileSize(), fileTask.costTimeMs(), fileTask.fileFullPath());
            // next file req
            if (status == StatusEnum.COMPLETED) {
                log.debug("+++++ ====== all file data (fileNo: {}, rpath: {}) receive complete. ======", fileNo, ClientFliIndexCache.getClientIndexNotUpdatePos(fileNo).getRpath());
                handleNext(ctx, rspFile, fileTask);
            }
        } else {
            log.error("recv file data chunk error, status.code: {}, status.msg: {}", status.code(), status.descp());
            // TODO handle client recv file data error...
            // already handle in TaskListener.onFail(FailEvent) method
        }
    }


    /**
     * handle next file list in fli.idx
     * send fileNo to server, server will get fileNo relative server file to create FileInputStream,
     * then chunk by chunk send to client
     *
     * @param ctx
     * @param rspFile
     */
    public void handleNext(ChannelHandlerContext ctx, RspFile rspFile, FileTask fileTask) {
        // request next file (info->data)
        // TODO check whether all files list in fli.idx have been receive.
        // fileNo should start from 1, min(1), fileNo = 0 has special meaning(stand for fli.idx file)
        // fileNo = 0, should only be handler in [REQ/RSP]_LIST_INFO/[REQ/RSP]_LIST_DATA
        if (rspFile.getFileNo() < ClientFliIndexCache.getMaxFileNo()) {
            MsgUtil.reqNextFileInfo(ctx);
        } else {
            long totalCostTimeMs = ClientCache.getTotalCostTimeMs();
            log.debug("all file transfer total cost: {} ms.", totalCostTimeMs);
        }
    }

    private void sendReqFileDataAck(ChannelHandlerContext ctx, long fileNo) {
        //        log.info("(fileNo: {}) Ack FILE DATA Chunk recv complete.", rspFile.getFileNo());
        ByteBuf out = MsgUtil.buildReq(BFileCmd.REQ_FILE_DATA_ACK, fileNo);
        ctx.writeAndFlush(out);
    }

}
